//
// Created by twetec on 25-9-5.
//

#include <gtest/gtest.h>
#include <mutex>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <netinet/ip6.h>
#include <netinet/icmp6.h>

// #include <future>

#include <netdb.h>
#include <network_prober/probe_detect/handle_icmp_probe_detect.h>

namespace tests::probe_detect {
namespace
{
TEST(HandleICMPProbeDetectIPv4Test, testDetect)
{
    // 加锁
    std::mutex mutx;
    std::condition_variable cv;

    {
        // 创建探测指令对象
        auto probeDetectInstructionPtr = std::make_shared<network_prober::probe_detect::HandleICMPProbeDetectIPv4>("192.168.0.108", "192.168.0.102");

        // 监听指令执行结果(延迟了对象生命周期)
        probeDetectInstructionPtr->setOnDetectedCallback([&cv, &mutx](network_prober::probe_detect::ICMPProbeDetectResultIPv4&& result) {
            std::unique_lock<std::mutex> lock(mutx);
            std::cout << result << std::endl;
            cv.notify_one();
        });

        // 执行指令
        probeDetectInstructionPtr->detect();
    }

    std::unique_lock<std::mutex> lock(mutx);
    cv.wait(lock);

    SUCCEED() << "执行成功";
}

/**
 * icmp ipv4 协议头
 */
// typedef struct
// {
//     std::uint8_t type;  // 类型(8=请求，0=应答)
//     std::uint8_t code;      // 代码(通常为0)
//     std::uint16_t checksum; // 校验和
//     std::uint16_t id;       // 标识符
//     std::uint16_t seq;      // 序列号
// } ICMPHeaderIPv4;

typedef struct
{
    struct icmphdr header;  // icmp v4 协议头
    struct timeval timestamp;  // 时间戳
    char payload[56];   //  负载
} ICMPPacketIPv4;

unsigned short computeICMPChecksumIPv4(unsigned short* addr, int len)
{
    int nleft = len;    // 数据包字节长度，用于计数未经过计算的字节数
    int sum = 0;
    unsigned short* w = addr;   // 数据包指针
    unsigned short answer = 0;  // 16位

    // 计算报文中两个字节的和
    while (nleft > 1) {
        sum += *w++;    // 1、从数据包中取16位；2、将取出的16位累加到和中 3、指针再移动16位，指向下两个要累加的数据字节内存
        nleft -= 2; // 将未计算的字节数减少2个，直到只剩1或0个字节
    }

    // 如果报文长度是奇数，最后一个字节作为高8位，再用0填充1个字节(低8位)
    if (nleft == 1) {
        *(unsigned char *)(&answer) = *(unsigned char *)w;  // 把16位类型指针转为8位类型指针，取剩下的1个字节复制给answer
        sum += answer;  // 将低位累加
    }

    sum = (sum >> 16) + (sum & 0xffff); // 高16位 + 低16位 = sum只剩16位
    sum += (sum >> 16);
    answer = ~sum;
    return answer;
}

TEST(HandleICMPProbeDetectIPv4Test, testPosix)
{
    // 创建UDP套接字
    int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        std::cerr << "Socket creation failed" << std::endl;
        FAIL();
    }

    // 设置发送超时
    struct timeval sendTimeout;
    sendTimeout.tv_sec = 5000 / 1000;
    sendTimeout.tv_usec = (5000 % 1000) * 1000;
    if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &sendTimeout, sizeof(sendTimeout)) < 0) {
        perror("setsockopt SO_SNDTIMEO");
        close(sockfd);
        FAIL();
    }

    // 设置接收超时
    struct timeval recvTimeout;
    recvTimeout.tv_sec = 5000 / 1000;
    recvTimeout.tv_usec = (5000 % 1000) * 1000;
    if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &recvTimeout, sizeof(recvTimeout)) < 0) {
        perror("setsockopt SO_SNDTIMEO");
        close(sockfd);
        FAIL();
    }

    // 设定目标地址
    struct sockaddr_in dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    if (inet_pton(AF_INET, "192.168.0.104", &dest_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address" << std::endl;
        close(sockfd);
        FAIL();
    }

    // 填充ICMP包
    ICMPPacketIPv4 packet;
    memset(&packet, 0, sizeof(packet));
    packet.header.type = ICMP_ECHO;
    packet.header.code = 0;
    packet.header.un.echo.id = getpid() & 0xFFFF;
    strncpy(packet.payload, "PING", sizeof(packet.payload)-1);
    // printf("PING %s (%s): %d data bytes\n", dest_ip, dest_ip,
    //        (int)(sizeof(packet) - sizeof(icmp_header_t)));

    packet.header.un.echo.sequence = 0;
    gettimeofday(&packet.timestamp, NULL);
    packet.header.checksum = 0;
    packet.header.checksum = computeICMPChecksumIPv4((unsigned short*)&packet, sizeof(packet));

    // 发送ICMP包
    if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) <= 0) {
        std::cout << "发送数据包失败.." << std::endl;
        FAIL();
    }

    // ip头部 + icmp头部 + icmp数据
    char recv_buf[1024];

    // struct sockaddr_in from_addr;
    // socklen_t from_len = sizeof(from_addr);

    // 接收响应
    ssize_t recv_len;
    socklen_t dest_len = sizeof(dest_addr);

    struct timeval start_time, end_time;
    gettimeofday(&start_time, NULL);

    recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&dest_addr, &dest_len);

    // 获取当前时间作为结束时间
    gettimeofday(&end_time, NULL);

    // 解析IP头获取ICMP包位置
    struct iphdr *ip_header = (struct iphdr *)recv_buf;
    size_t ip_header_len = ip_header->ihl * 4;

    if (recv_len < ip_header_len + ICMP_MINLEN) {
        std::cout << "包长度不足" << std::endl;
        // continue;
    }

    struct icmphdr *icmp_header = (struct icmphdr *)(recv_buf + ip_header_len);

    // 检查是否是我们的响应
    if (icmp_header->type == ICMP_ECHOREPLY && icmp_header->un.echo.id == packet.header.un.echo.id) {

        double rtt = (end_time.tv_sec - start_time.tv_sec) * 1000.0 +
                    (end_time.tv_usec - start_time.tv_usec) / 1000.0;

        std::cout << (int)(recv_len - ip_header_len)
            << "bytes from" << inet_ntoa(dest_addr.sin_addr)
            << ": icmp_seq=" << ntohs(icmp_header->un.echo.sequence)
            << " ttl=" << ip_header->ttl
            << " time=" << rtt << std::endl;

        // printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.3f ms\n",
        //        (int)(recv_len - ip_header_len),
        //        inet_ntoa(from_addr.sin_addr),
        //        ntohs(icmp_header->un.echo.sequence),
        //        ip_header->ttl,
        //        rtt);
        // break;
    }

    close(sockfd);
}

TEST(HandleICMPProbeDetectIPv4Test, testGetaddrinfo)
{
    // 指定想获取的ip类型
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;  // IPv4
    hints.ai_socktype = SOCK_DGRAM; // TCP 套接字

    // 获取到的ip地址，存放在result中
    struct addrinfo *result;

    // 请求域名解析
    int status = 0;
    if ((status = getaddrinfo("www.jjwxc.net", nullptr, &hints, &result)) != 0) {
        std::cout << "解析出错: " << gai_strerror(status) << std::endl;
        return;
    }

    std::cout << "解析成功" << std::endl;

    struct addrinfo* cur;
    struct sockaddr_in* addr;
    char ipBuffer[INET_ADDRSTRLEN];
    int port;

    for (cur = result; cur != NULL; cur = cur->ai_next) {
        addr = (struct sockaddr_in*)cur->ai_addr;
        std::cout << "ip: " << inet_ntop(AF_INET, &addr->sin_addr, ipBuffer, INET_ADDRSTRLEN) << std::endl;
        // std::cout << "port: " << ntohs(addr) << std::endl;
    }

    freeaddrinfo(result);
}

typedef struct
{
    struct icmp6_hdr header;  // icmp v4 协议头
    struct timeval tv;  // 时间戳
    char payload[64-sizeof(icmp6_hdr)];   //  负载
} ICMPPacketIPv6;

unsigned short computeICMPChecksumIPv6(unsigned short* addr, int len)
{
    int nleft = len;    // 数据包字节长度，用于计数未经过计算的字节数
    int sum = 0;
    unsigned short* w = addr;   // 数据包指针
    unsigned short answer = 0;  // 16位

    // 计算报文中两个字节的和
    while (nleft > 1) {
        sum += *w++;    // 1、从数据包中取16位；2、将取出的16位累加到和中 3、指针再移动16位，指向下两个要累加的数据字节内存
        nleft -= 2; // 将未计算的字节数减少2个，直到只剩1或0个字节
    }

    // 如果报文长度是奇数，最后一个字节作为高8位，再用0填充1个字节(低8位)
    if (nleft == 1) {
        *(unsigned char *)(&answer) = *(unsigned char *)w;  // 把16位类型指针转为8位类型指针，取剩下的1个字节复制给answer
        sum += answer;  // 将低位累加
    }

    sum = (sum >> 16) + (sum & 0xffff); // 高16位 + 低16位 = sum只剩16位
    sum += (sum >> 16);
    answer = ~sum;
    return answer;
}

TEST(HandleICMPProbeDetectIPv6Test, testPosix)
{
    // 创建UDP套接字
    int sockfd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
    if (sockfd < 0) {
        std::cerr << "Socket creation failed" << std::endl;
        FAIL();
    }

    // 设置发送超时
    // struct timeval sendTimeout;
    // sendTimeout.tv_sec = 5000 / 1000;
    // sendTimeout.tv_usec = (5000 % 1000) * 1000;
    // if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &sendTimeout, sizeof(sendTimeout)) < 0) {
    //     perror("setsockopt SO_SNDTIMEO");
    //     close(sockfd);
    //     FAIL();
    // }

    // 设置接收超时
    struct timeval recvTimeout;
    recvTimeout.tv_sec = 5000 / 1000;
    recvTimeout.tv_usec = (5000 % 1000) * 1000;
    if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &recvTimeout, sizeof(recvTimeout)) < 0) {
        perror("setsockopt SO_SNDTIMEO");
        close(sockfd);
        FAIL();
    }

    // 设定目标地址
    struct sockaddr_in6 dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin6_family = AF_INET6;
    if (inet_pton(AF_INET6, "fd00:7860:5bfc:44d0::1001", &dest_addr.sin6_addr) <= 0) {
        std::cerr << "Invalid address" << std::endl;
        close(sockfd);
        FAIL();
    }

    // 填充ICMP包
    ICMPPacketIPv6 sendPacket;
    memset(&sendPacket, 0, sizeof(sendPacket));
    sendPacket.header.icmp6_type = ICMP6_ECHO_REQUEST;
    sendPacket.header.icmp6_code = 0;
    sendPacket.header.icmp6_cksum = 0;  // checksum will be calculated later
    sendPacket.header.icmp6_id = htons(getpid() & 0xFFFF);  // Identifier
    sendPacket.header.icmp6_seq = htons(1);  // Sequence number
    // char msg[] = "Hello, world!";
    // memcpy(sendPacket.icmp6_data8, msg, sizeof(msg));
    strncpy(sendPacket.payload, "PING", sizeof(sendPacket.payload)-1);


    // 发送ICMP包
    if (sendto(sockfd, &sendPacket, sizeof(sendPacket), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) <= 0) {
        std::cout << "发送数据包失败: " << strerror(errno) << std::endl;
        FAIL();
    }

    // ip头部 + icmp头部 + icmp数据
    char recv_buf[1024];

    // struct sockaddr_in from_addr;
    // socklen_t from_len = sizeof(from_addr);

    // 接收响应
    ssize_t recv_len;
    socklen_t dest_len = sizeof(dest_addr);

    struct timeval start_time, end_time;
    gettimeofday(&start_time, NULL);

    recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&dest_addr, &dest_len);

    // 获取当前时间作为结束时间
    gettimeofday(&end_time, NULL);

    struct icmp6_hdr *icmp6_recv = (struct icmp6_hdr *)recv_buf;

    std::cout << "占位" << std::endl;

    // // 解析IP头获取ICMP包位置
    // struct iphdr *ip_header = (struct iphdr *)recv_buf;
    // size_t ip_header_len = ip_header->ihl * 4;
    //
    // if (recv_len < ip_header_len + ICMP_MINLEN) {
    //     std::cout << "包长度不足" << std::endl;
    //     // continue;
    // }
    //
    // struct icmphdr *icmp_header = (struct icmphdr *)(recv_buf + ip_header_len);
    //
    // 检查是否是我们的响应
    if (icmp6_recv->icmp6_type == ICMP6_ECHO_REPLY) {

        double rtt = (end_time.tv_sec - start_time.tv_sec) * 1000.0 +
                    (end_time.tv_usec - start_time.tv_usec) / 1000.0;

        std::cout << "rtt: " << rtt << std::endl;

        // std::cout << (int)(recv_len - ip_header_len)
        //     << "bytes from" << inet_ntoa(dest_addr.sin_addr)
        //     << ": icmp_seq=" << ntohs(icmp_header->un.echo.sequence)
        //     << " ttl=" << ip_header->ttl
        //     << " time=" << rtt << std::endl;

        // printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.3f ms\n",
        //        (int)(recv_len - ip_header_len),
        //        inet_ntoa(from_addr.sin_addr),
        //        ntohs(icmp_header->un.echo.sequence),
        //        ip_header->ttl,
        //        rtt);
        // break;
    }

    close(sockfd);
}

TEST(HandleICMPProbeDetectIPv6Test, testDetect)
{
    // 创建探测指令对象
    auto probeDetectInstructionPtr = std::make_shared<network_prober::probe_detect::HandleICMPProbeDetectIPv6>("fd00:7860:5bfc:44d0:9d7e:1f46:6ad7:89c", "fd00:7860:5bfc:44d0::1001");

    // 监听指令执行结果(延迟了对象生命周期)
    probeDetectInstructionPtr->setOnDetectedCallback([](network_prober::probe_detect::ICMPProbeDetectResultIPv6&& result) {
        std::cout << result << std::endl;
    });

    // 执行指令
    probeDetectInstructionPtr->detect();

    while (getchar() != '\n');
}

}
}
